package org.jinyan.unused.socket;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class EchoServer {

	private static int SOCKET_NUM = 55555;

	private static DateFormat dateFormatter = new SimpleDateFormat(
			"yyyyMMdd HH:mm:ss");

	/*    
    *//**
	 * @param args
	 */
	/*
	 * public static void main(String[] args) { new EchoServer().start(); }
	 */
	public void start() {
		try {
			Selector selector = bindServer(); // 绑定服务端口，并定义一个事件选择器对象记录套接字通道的事件

			/* 通过此循环来遍例事件 */
			while (true) {
				log("Waiting events.");
				int n = selector.select(); // 查询事件如果一个事件都没有，这里就会阻塞
				log("Got events: " + n);

				ByteBuffer echoBuffer = ByteBuffer.allocate(50); // 定义一个byte缓冲区来存储收发的数据

				/* 循环遍例所有产生的事件 */
				for (SelectionKey key : selector.selectedKeys()) {
					SocketChannel sc;
					selector.selectedKeys().remove(key); // 将本此事件从迭带器中删除

					/* 如果产生的事件为接受客户端连接(当有客户端连接服务器的时候产生) */
					if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {

						ServerSocketChannel subssc = (ServerSocketChannel) key
								.channel(); // 定义一个服务器socket通道

						sc = subssc.accept(); // 将临时socket对象实例化为接收到的客户端的socket

						sc.configureBlocking(false); // 将客户端的socket设置为异步

						sc.register(selector, SelectionKey.OP_READ); // 将客户端的socket的读取事件注册到事件选择器中

						System.out.println("Got new client:" + sc);
					}
					/* 如果产生的事件为读取数据(当已连接的客户端向服务器发送数据的时候产生) */
					else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {

						sc = (SocketChannel) key.channel(); // 临时socket对象实例化为产生本事件的socket

						ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 定义一个用于存储byte数据的流对象，存储全部信息

						echoBuffer.clear(); // 先将客户端的数据清空

						try {
							// 循环读取所有客户端数据到byte缓冲区中,当有数据的时候read函数返回数据长度
							// NIO会自动的将缓冲区一次容纳不下的自动分段
							int readInt = 0; // 为读取到数据的长度
							while ((readInt = sc.read(echoBuffer)) > 0) {
								// 如果获得数据长度比缓冲区大小小的话
								if (readInt < echoBuffer.capacity()) {

									byte[] readByte = new byte[readInt]; // 建立一个临时byte数组,将齐长度设为获取的数据的长度
									// 循环向此临时数组中添加数据
									for (int i = 0; i < readInt; i++) {
										readByte[i] = echoBuffer.get(i);
									}

									bos.write(readByte); // 将此数据存入byte流中
								}
								// 否则就是获得数据长度等于缓冲区大小
								else {
									bos.write(echoBuffer.array()); // 将读取到的数据写入到byte流对象中
								}
							}
							// 当循环结束时byte流中已经存储了客户端发送的所有byte数据
							log("Recive msg: " + new String(bos.toByteArray()));
						} catch (Exception e) {

							e.printStackTrace(); // 当客户端在读取数据操作执行之前断开连接会产生异常信息

							key.cancel(); // 将本socket的事件在选择器中删除
							break;
						}

						writeBack(sc, bos.toByteArray()); // 向客户端写入收到的数据
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 绑定服务端口，初始化整个服务
	 * 
	 * @throws IOException
	 */
	private Selector bindServer() throws IOException {
		log("Start binding server socket：" + SOCKET_NUM);

		Selector selector = Selector.open(); // 定义一个事件选择器对象记录套接字通道的事件

		ServerSocketChannel ssc = ServerSocketChannel.open(); // 定义一个异步服务器socket对象

		ssc.configureBlocking(false);// 将此socket对象设置为异步

		ServerSocket ss = ssc.socket(); // 定义服务器socket对象-用来指定异步socket的监听端口等信息

		InetSocketAddress address = new InetSocketAddress(SOCKET_NUM); // 定义存放监听端口的对象

		ss.bind(address); // 将服务器与这个端口绑定

		ssc.register(selector, SelectionKey.OP_ACCEPT); // 将异步的服务器socket对象的接受客户端连接事件注册到selector对象内

		log("Binded socket at：" + SOCKET_NUM);

		return selector;
	}

	private boolean writeBack(SocketChannel sc, byte[] b) {
		ByteBuffer echoBuffer = ByteBuffer.allocate(b.length); // 建立这个byte对象的ByteBuffer
		echoBuffer.put(b); // 将数据存入

		echoBuffer.flip(); // 将缓冲区复位以便于进行其他读写操作
		try {
			// 向客户端写入数据,数据为接受到数据
			sc.write(echoBuffer);
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		System.out.println("Msg echo back: " + new String(echoBuffer.array()));
		return true;
	}

	private static void log(Object msg) {
		System.out.println("SERVER [" + dateFormatter.format(new Date())
				+ "]: " + msg);
	}
}
